import {
  ATTR_HTTP_REQUEST_METHOD,
  ATTR_HTTP_REQUEST_METHOD_ORIGINAL,
  ATTR_URL_FULL,
  ATTR_URL_PATH,
  ATTR_URL_QUERY,
  ATTR_URL_SCHEME,
  ATTR_SERVER_ADDRESS,
  ATTR_SERVER_PORT,
  ATTR_ERROR_TYPE,
  METRIC_HTTP_CLIENT_REQUEST_DURATION,
} from '@opentelemetry/semantic-conventions';

import { trace, context, propagation, SpanKind, metrics, ValueType } from '@/plugins/opentelemetry';

/**
 * 获取 tracer
 */
const tracer = trace.getTracer('fetch');

/**
 * 获取 meter
 */
const meter = metrics.getMeter('fetch');

/**
 * 创建 fetch 请求时间 histogram
 */
const fetchHistogram = meter.createHistogram(METRIC_HTTP_CLIENT_REQUEST_DURATION, {
  description: 'Measures the duration of outbound HTTP requests.',
  unit: 'ms',
  valueType: ValueType.INT,
  advice: {
    explicitBucketBoundaries: [
      5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000,
    ],
  },
});

/**
 * 请求信息类型
 */
type Input = string | URL | Request;

/**
 * Instrument Fetch. 插桩请求
 * @param {Input} input 请求信息
 * @param {RequestInit} init 请求初始化
 * @returns {Promise<Response>} 请求响应
 */
export default async function ifetch(input: Input, init?: RequestInit): Promise<Response> {
  // 创建 URL 和 Request
  let url: URL;
  let request: Request;
  if (input instanceof Request) {
    url = new URL(input.url);
    request = input;
  } else {
    url = typeof input === 'string' ? new URL(input) : input;
    request = new Request(input.toString(), init);
  }

  // 获取 URL Schema
  const schema = url.protocol.substring(0, url.protocol.length - 1);
  let schemaPort: string | undefined = undefined;
  if (url.port) {
    schemaPort = url.port;
  } else if (schema.startsWith('http')) {
    schemaPort = schema.endsWith('s') ? '443' : '80';
  }

  // 设置 attributes
  const attributes = {
    [ATTR_HTTP_REQUEST_METHOD]: request.method,
    [ATTR_HTTP_REQUEST_METHOD_ORIGINAL]: init?.method,
    [ATTR_URL_FULL]: url.toString(),
    [ATTR_URL_PATH]: url.pathname,
    [ATTR_URL_QUERY]: url.search,
    [ATTR_URL_SCHEME]: schema,
    [ATTR_SERVER_ADDRESS]: url.hostname,
    [ATTR_SERVER_PORT]: schemaPort,
  };

  // 创建 span
  const span = tracer.startSpan(`Fetch ${request.method} ${url}`, {
    kind: SpanKind.CLIENT,
    attributes,
  });
  trace.setSpan(context.active(), span);

  // 注入 context 到 headers
  const addedHeaders = {};
  propagation.inject(context.active(), addedHeaders);
  for (const [k, v] of Object.entries(addedHeaders)) {
    request.headers.append(k, v as string);
  }

  // 记录开始时间
  const startTime = Date.now();

  return fetch(request)
    .catch(error => {
      // 记录错误信息
      if (error instanceof Object && 'message' in error) {
        span.setAttributes({
          [ATTR_ERROR_TYPE]: error.message,
        });
      }

      throw error;
    })
    .finally(() => {
      // 记录时间
      fetchHistogram.record(Date.now() - startTime, attributes);

      // 结束 span
      span.end();
    });
}
